//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/StateManager.h"

namespace Engine
{
namespace Graphics
{
	using namespace Base;
	
	class Viewport;
	SHARED_POINTER( Viewport );
	


	//
	// Viewport
	//

	class Viewport : public BaseObject
	{
	// types
	public:
		struct EMode2D
		{
			enum type
			{
				AUTO	= 0,

				SNORM,					// -1..+1
				UNORM,					//  0..+1

				DIPS,					// 0..dips
				PIXELS,					// 0..pixels

				SNORM_PROPORTIONAL,		// size * aspect
				UNORM_PROPORTIONAL,		// size * aspect
			};
		};


	// variables
	private:
		mat4			_orthoMatrix,
						_flipOrthoMatrix;

		rect			_viewRect;
		ivec4			_viewport;

		float			_aspect;			// width / height

		vec2			_resCorrect;		// resolution correction
		vec2			_resCorAspect;		// resolution correction aspect

		EMode2D::type	_mode2d;


	// methods
	private:
		ScreenCoords::Pixels	_GetSizeInPixels () const;

	public:
		Viewport (const EngineSubSystemsRef ess);

		const vec2 &	GetResCorrect ()		const	{ return _resCorrect; }
		const vec2 &	GetResCorrectAspect ()	const	{ return _resCorAspect; }
		
		const ivec4 &	ViewportRect ()			const	{ return _viewport; }
		const rect &	ViewRect ()				const	{ return _viewRect; }
		const ivec2 	Size ()					const	{ return _viewport.zw(); }
		float			Aspect ()				const	{ return _aspect; }
		EMode2D::type	GetMode2D ()			const	{ return _mode2d; }
		
		bool			IsHorizontal ()			const	{ const ivec2 size = Size();  return size.x > size.y; }
		bool			IsVertical ()			const	{ return not IsHorizontal(); }

		Display const &	GetDisplay ()			const;
		const vec2		SizeInDips ()			const;
		const vec2		PhysicsSize ()			const;

		const vec2		PositionToNormalized (const vec2 &pixels)		const;
		const vec2		PositionToNormalized (const ivec2 &pixels)		const	{ return PositionToNormalized( pixels.To<vec2>() ); }

		const vec2		DeltaToNormalized (const vec2 &pixels)			const;
		const vec2		DeltaToNormalized (const ivec2 &pixels)			const	{ return DeltaToNormalized( pixels.To<vec2>() ); }

		const vec2		PositionToProportional (const vec2 &pixels)		const;
		const vec2		PositionToProportional (const ivec2 &pixels)	const	{ return PositionToProportional( pixels.To<vec2>() ); }

		const vec2		DeltaToProportional (const vec2 &pixels)		const;
		const vec2		DeltaToProportional (const ivec2 &pixels)		const	{ return DeltaToProportional( pixels.To<vec2>() ); }

		const ivec2		PositionToPixels (const vec2 &norm)	const;
		const ivec2		DeltaToPixels (const vec2 &norm)	const;
		

		void SetViewport (const ivec4 &viewport);
		void Setup2DMode (const rect &viewRect, const vec2 &clipPlanes = vec2(-1.0f, 1.0f));
		void Setup2DMode (EMode2D::type type);
		void UpdateCorrection ();
		void Apply ();
		void Mode2D (bool flip = false);


		static ViewportPtr New (const EngineSubSystemsRef ess);
	};



	
/*
=================================================
	constructor
=================================================
*/
	inline Viewport::Viewport (const EngineSubSystemsRef ess) :
		BaseObject(ess), _aspect(1.0f),
		_viewRect(-1.0f, -1.0f, 1.0f, 1.0f),
		_viewport(0, 0, 240, 320),
		_mode2d(EMode2D::AUTO)
	{}

/*
=================================================
	GetDisplay
=================================================
*/
	inline Display const & Viewport::GetDisplay () const
	{
		return ESS()->GetApplication()->GetPlatform()->GetDisplay();
	}
	
/*
=================================================
	_GetSizeInPixels
=================================================
*/
	inline ScreenCoords::Pixels Viewport::_GetSizeInPixels () const
	{
		return DeviceEmulator::ToDeviceScreen( GetDisplay(), ScreenCoords::Pixels( Size() ) );
	}

/*
=================================================
	SizeInDips
=================================================
*/
	inline const vec2 Viewport::SizeInDips () const
	{
		return GetDisplay().ToDip( _GetSizeInPixels() ).Get();
	}
	
/*
=================================================
	PhysicsSize
=================================================
*/
	inline const vec2 Viewport::PhysicsSize () const
	{
		return GetDisplay().ToPhysics( _GetSizeInPixels() ).Get();
	}
	
/*
=================================================
	DeltaToNormalized
=================================================
*/
	inline const vec2 Viewport::DeltaToNormalized (const vec2 &pixels) const
	{
		return ( pixels / Size().To<vec2>() ) * _viewRect.GetSize();
	}
	
/*
=================================================
	PositionToNormalized
=================================================
*/
	inline const vec2 Viewport::PositionToNormalized (const vec2 &pixels) const
	{
		return DeltaToNormalized( pixels ) + _viewRect.LeftBottom();
	}
	
/*
=================================================
	DeltaToProportional
=================================================
*/
	inline const vec2 Viewport::DeltaToProportional (const vec2 &pixels) const
	{
		return DeltaToNormalized( pixels ) * GetResCorrectAspect();
	}
	
/*
=================================================
	PositionToProportional
=================================================
*/
	inline const vec2 Viewport::PositionToProportional (const vec2 &pixels) const
	{
		return PositionToNormalized( pixels ) * GetResCorrectAspect();
	}

/*
=================================================
	PositionToPixels
=================================================
*/
	inline const ivec2 Viewport::PositionToPixels (const vec2 &norm) const
	{
		return DeltaToPixels( norm - _viewRect.LeftBottom() );
	}
	
/*
=================================================
	DeltaToPixels
=================================================
*/
	inline const ivec2 Viewport::DeltaToPixels (const vec2 &norm) const
	{
		return Round<int>( ( norm / _viewRect.GetSize()) * Size().To<vec2>() );
	}

/*
=================================================
	SetViewport
=================================================
*/
	inline void Viewport::SetViewport (const ivec4 &viewport)
	{
		_viewport	= viewport;
		_aspect		= (float)_viewport[2] / (float)_viewport[3];
	}

/*
=================================================
	Setup2DMode
=================================================
*/
	inline void Viewport::Setup2DMode (const rect &viewRect, const vec2 &clipPlanes)
	{
		_viewRect			= viewRect;
		_orthoMatrix		= mat4::BuildOrtho( _viewRect, clipPlanes );
		_flipOrthoMatrix	= mat4::BuildOrtho( frect( _viewRect.left, _viewRect.top,
														_viewRect.right, _viewRect.bottom ), clipPlanes );
		UpdateCorrection();
	}
	
/*
=================================================
	Setup2DMode
=================================================
*/
	inline void Viewport::Setup2DMode (EMode2D::type type)
	{
		_mode2d = type;

		switch ( type )
		{
			case EMode2D::AUTO :
				break;

			case EMode2D::SNORM :	return Setup2DMode( rect(-1.0f, -1.0f, 1.0f, 1.0f) );
			case EMode2D::UNORM :	return Setup2DMode( rect( 0.0f,  0.0f, 1.0f, 1.0f) );

			case EMode2D::DIPS :	return Setup2DMode( rect( vec2(0.0f), GetDisplay().CalcDip().Get() ) );

			case EMode2D::PIXELS :	return Setup2DMode( rect( vec2(0.0f), GetDisplay().Resolution().To<vec2>() ) );

			case EMode2D::SNORM_PROPORTIONAL : {
				const vec2	size  = Size().To<vec2>();
				const vec2	scale = size / size.Max();

				rect	r(-1.0f, -1.0f, 1.0f, 1.0f);

				Setup2DMode( r * scale );
				ASSERT( All( _resCorAspect == vec2(1.0f) ) );
				return;
			}

			case EMode2D::UNORM_PROPORTIONAL :{
				const vec2	size  = Size().To<vec2>();
				const vec2	scale = size / size.Max();

				rect	r(0.0f, 0.0f, 1.0f, 1.0f);

				Setup2DMode( r * scale );
				ASSERT( All( _resCorAspect == vec2(1.0f) ) );
				return;
			}
		}

		_mode2d = EMode2D::AUTO;
		return Setup2DMode( rect(-1.0f, -1.0f, 1.0f, 1.0f) );
	}

/*
=================================================
	UpdateCorrection
=================================================
*/
	inline void Viewport::UpdateCorrection ()
	{
		_resCorrect		= _viewRect.GetSize() / Size().To<vec2>();
		_resCorAspect	= _resCorrect.x > _resCorrect.y ?
							vec2( 1.0f, _resCorrect.y / _resCorrect.x ) :
							vec2( _resCorrect.x / _resCorrect.y, 1.0f );
	}
	
/*
=================================================
	Apply
=================================================
*/
	inline void Viewport::Apply ()
	{
		ESS()->GetGLStateManager()->Viewport( _viewport );
	}
	
/*
=================================================
	Mode2D
=================================================
*/
	inline void Viewport::Mode2D (bool flip)
	{
		const mat4 & mvp = flip ? _flipOrthoMatrix : _orthoMatrix;

		ESS()->GetGLStateManager()->SetModelViewProjectionMatrix( mat4::Identity(), mat4::Identity(), mvp, mvp );
	}
/*
=================================================
	New
=================================================
*/
	inline ViewportPtr Viewport::New (const EngineSubSystemsRef ess)
	{
		return BaseObject::_New( new Viewport( ess ) );
	}


}	// Graphics
}	// Engine